﻿using LeRenard.ScriptWars.Model.Entities;
using LeRenard.ScriptWars.Model.GridSystem;
using LeRenard.ScriptWars.Model.Scripting.OperationParameterTypeValueConverters;
using System;
using System.Collections.Generic;
using System.Linq;

namespace LeRenard.ScriptWars.Model.Scripting.Definitions
{
    /// <summary>
    /// Contains the known <see cref="IOperationDefinition"/>s.
    /// </summary>
    public static class OperationDefinitionRepository
    {
        #region Properties
        /// <summary>
        /// Gets all the <see cref="IOperationDefinition"/>s.
        /// </summary>
        public static IEnumerable<IOperationDefinition> OperationDefinitions { get { return _operationDefinitions; } }
        private static readonly IList<IOperationDefinition> _operationDefinitions;
        #endregion

        #region Construction
        /// <summary>
        /// Creates/initializes the repository.
        /// </summary>
        static OperationDefinitionRepository()
        {
            _operationDefinitions = new List<IOperationDefinition>();
            foreach (var operation in GetCoreOperations()) {
                _operationDefinitions.Add(operation);
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the core (or "baked-in" operations).
        /// </summary>
        /// <returns></returns>
        private static IEnumerable<IOperationDefinition> GetCoreOperations()
        {
            var booleanConverter = new BooleanParameterTypeValueConverter();
            var integerConverter = new IntegerParameterTypeValueConverter();
            var stringConverter = new StringParameterTypeValueConverter();

            Func<IEntity, IOperation, bool> canDoNextTurn = (e, o) => {

                if (e.PreviousOperations.Count() == 0) {
                    return true;
                }

                if (e.PreviousOperations.Count() >= 2 && (o.Definition.Name.Equals("GOTO") || o.Definition.Name.Equals("RANDOM"))) {
                    return true;
                }

                e.ResetPreviousOperations();
                return false;
            };

            var random = new Random();

            yield return OperationDefinitionFactory
                .New("MOVE")
                .AddExecutionMethod((e, a, g, o) => {
                    if (canDoNextTurn(e, o)) {

                        // Perform operation
                        var oldPosition = g.Grid.GetPositionFor(e); // getting current position
                        var xModifier = 0;
                        var yModifier = 0;
                        if (e.Direction == Direction.North) {
                            if (oldPosition.Y != 0) {
                                yModifier = -1;
                            }
                        }
                        if (e.Direction == Direction.East) {
                            if (oldPosition.X != g.Grid.Size.Width - 1) {
                                xModifier = 1;
                            }
                        }
                        if (e.Direction == Direction.South) {
                            if (oldPosition.Y != g.Grid.Size.Height - 1) {
                                yModifier = 1;
                            }
                        }
                        if (e.Direction == Direction.West) {
                            if (oldPosition.X != 0) {
                                xModifier = -1;
                            }
                        }
                        var newPosition = new GridPosition(oldPosition.X + xModifier, oldPosition.Y + yModifier);

                        if (!(newPosition.X == oldPosition.X && newPosition.Y == oldPosition.Y)) {
                            var cell = g.Grid.Layout[oldPosition.Y, oldPosition.X]; // do the switch
                            g.Grid.Layout[newPosition.Y, newPosition.X] = cell;
                            g.Grid.Layout[oldPosition.Y, oldPosition.X] = new GridCell<IEntity>(); // empty old value
                        }

                        a.Next(); // cycling the algorithm line number    
                        e.ResetPreviousOperations();
                    }
                })
                .Build();

            yield return OperationDefinitionFactory
                .New("TURN")
                .AddExecutionMethod((e, a, g, o) => {
                    if (canDoNextTurn(e, o)) {
                        // Get parameters
                        var directionValue = o.Parameters.Where(p => p.Definition.Name.Equals("Direction")).Single().Value;
                        var direction = stringConverter.ConvertFromScriptValue(directionValue);

                        // Perform operation
                        e.ChangeDirection(direction); // instruct entity to change it's direction
                        a.Next(); // cycling the algorithm line number
                        e.ResetPreviousOperations();
                    }
                })
                .AddParameter("Direction", OperationParameterType.String)                
                .Build();

            yield return OperationDefinitionFactory
                .New("RANDOM")
                .AddExecutionMethod((e, a, g, o) => {
                    if (canDoNextTurn(e, o)) {
                        // Get parameters
                        var randomChanceValue = o.Parameters.Where(p => p.Definition.Name.Equals("Chance")).Single().Value;
                        var randomChance = integerConverter.ConvertFromScriptValue(randomChanceValue);
                        var lineNumberValue = o.Parameters.Where(p => p.Definition.Name.Equals("LineNumber")).Single().Value;
                        var lineNumber = integerConverter.ConvertFromScriptValue(lineNumberValue);

                        // Perform operation
                        var randomNumber = random.Next(0, 100);
                        if (randomNumber > randomChance) {
                            // Chance 1
                            a.Reset(lineNumber); // going to the line number
                            e.NextTurn(g); // executing the new line number / operation
                        } else {
                            // Chance 2
                            a.Next(); // cycling the algorithm line number
                            e.ResetPreviousOperations();
                        }
                    }
                })
                .AddParameter("Chance", OperationParameterType.Integer)
                .AddParameter("LineNumber", OperationParameterType.Integer)
                .Build();

            yield return OperationDefinitionFactory
                .New("GOTO")
                .AddExecutionMethod((e, a, g, o) => {
                    if (canDoNextTurn(e, o)) {
                        // Get parameters
                        var lineNumberValue = o.Parameters.Where(p => p.Definition.Name.Equals("LineNumber")).Single().Value;
                        var lineNumber = integerConverter.ConvertFromScriptValue(lineNumberValue);

                        // Perform operation
                        a.Reset(lineNumber); // going to the line number
                        e.NextTurn(g); // executing the new line number / operation
                    }
                })
                .AddParameter("LineNumber", OperationParameterType.Integer)
                .Build();
        }
        #endregion
    }
}
